#include <new>

#include "tdp2_maincontext.hpp"
#include "../../rdx/src/rdx/rdx.h"
#include "../../rdx/src/rdx/rdx_objectloader.hpp"

#include "tdp2_visualresources.hpp"

using namespace RDX::ObjectManagement;

namespace TDP
{
	using namespace ResourceManagement;
	using namespace Containers;

	void *MainContext::WrappedRealloc(void *opaque, void *ptr, LargeInt sz, LargeInt align)
	{
		MainContext *ctx = static_cast<MainContext *>(opaque);
		return ctx->_baseAlloc.reallocFunc(ctx->_baseAlloc.opaque, ptr, sz, align);
	}

	static const RDX::ObjectManagement::INativeTypeHost *standardPlugins[] =
	{
		RDX_GetNumericTypesPlugin(),
		TDP::Resources::GetVisualResourcesPluginIntegrated(),
		NULL,
	};

	MainContext::MainContext(const TDP::PanicHandler *ph, const RDX::ObjectManagement::INativeTypeHost **plugins,
		const RDX::ObjectManagement::Allocator &allocator, RDX::IO::IFileSystem *fs, TDP::Rendering::IRenderDevice *renderDevice) :
			_plugins(allocator)
	{
		static bool rdxInitialized = false;

		if(!rdxInitialized)
		{
			RDX_Initialize();
			rdxInitialized = true;
		}

		_lastError = ERR_None;
		_baseAlloc = allocator;
		_mainFS = fs;
		_renderDevice = renderDevice;
		_rdxCodeProvider = NULL;

		_alloc.opaque = this;
		_alloc.reallocFunc = WrappedRealloc;

		_firstPendingStream = _lastPendingStream = NULL;

		// Set up the plugins list
		LargeUInt numStandardPlugins = 0;
		for(LargeUInt i=0;standardPlugins[i];i++)
			numStandardPlugins++;

		LargeUInt numAddedPlugins = 0;
		if(plugins)
			for(LargeUInt i=0;plugins[i];i++)
				numAddedPlugins++;

		if(!_plugins.TryResize(numStandardPlugins + numAddedPlugins + 1))
		{
			_lastError = ERR_InitializeFailed;
			return;
		}
		
		for(LargeUInt i=0;i<numStandardPlugins;i++)
			_plugins[i] = standardPlugins[i];
		for(LargeUInt i=0;i<numAddedPlugins;i++)
			_plugins[numStandardPlugins + i] = plugins[i];

		_plugins[numStandardPlugins + numAddedPlugins] = NULL;

		// Init the main type host now that we have a full plugin set
		TDP_HNEW(&_typeHost, CombinedNativeTypeHost, (_plugins));

		// Init the code provider
		_rdxCodeProvider = RDX_CreateInterpreterCodeProvider(&_alloc, NULL, 0);
		if(!_rdxCodeProvider)
		{
			_lastError = ERR_InitializeFailed;
			return;
		}


		TDP_HNEW(&_mfpRM, MFPResourceManager, (fs, _alloc));
		TDP_HNEW(&_mfpFS, MFPFileSystem, (_mainFS, _mfpRM));

		_objm = RDX_CreateObjectManager(&_alloc, _typeHost, _rdxCodeProvider);
		if(!_objm)
		{
			_lastError = ERR_InitializeFailed;
			return;
		}

		{
			RDX::OperationContext ctx(_objm);
			RDX_TRY(&ctx)
			{
				TDP_HNEW(&_zonePH, RDX::Security::ZonePolicyPackageHost, (&ctx, _objm, _mfpFS, false));

				Utility::RuntimeObjectLoaderHost zoneLoaderHost(_zonePH, DOMAIN_Runtime, _mfpFS);
				zoneLoaderHost.SetPackage(RDX_STATIC_STRING("Game.zonepolicy"), true);

				CRef<Security::Zone*> zones;
				RDX_PROTECT_ASSIGN(&ctx, zones, _objm->LoadObject(&ctx, &zoneLoaderHost).Cast<Security::Zone*>());
				RDX_PROTECT(&ctx, _zonePH->LoadZones(&ctx, zones));
			}
			RDX_CATCH(&ctx)
			{
				_lastError = ERR_InitializeFailed;
				return;
			}
			RDX_ENDTRY
		}

		
		TDP_HNEW(&_preloader, ResourcePreloader, (ph, _alloc, 3000000, sizeof(void*), _mfpRM));
		TDP_HNEW(&_lrm, Resources::LiveResourceManager, (_alloc, this));

		if(!_lrm->Initialize(10000, 65536))
		{
			_lastError = ERR_InitializeFailed;
			return;
		}

		// Load standard resources
		{
			RDX::OperationContext ctx(_objm);
			RDX_NTRY(EH2, &ctx)
			{
				CRef<const String> str;
				RDX_NPROTECT_ASSIGN(EH2, &ctx, str, _objm->CreateString(&ctx, RDX_STATIC_STRING("TDP.Resources.Shaders.CoreShaders.UIGraphic")));
				RDX_NPROTECT_ASSIGN(EH2, &ctx, _coreShaders[CoreShaders::CS_UIGraphic], _objm->LookupSymbol(&ctx, str, _zonePH.Ptr()).Cast<Resources::ShaderInfo::ShaderInfo>());

				RDX_NPROTECT_ASSIGN(EH2, &ctx, str, _objm->CreateString(&ctx, RDX_STATIC_STRING("TDP.Resources.Shaders.CoreShaders.WorldSurfaceGeneric")));
				RDX_NPROTECT_ASSIGN(EH2, &ctx, _coreShaders[CoreShaders::CS_WorldSurfaceGeneric], _objm->LookupSymbol(&ctx, str, _zonePH.Ptr()).Cast<Resources::ShaderInfo::ShaderInfo>());
			}
			RDX_NCATCH(EH2, &ctx)
			{
				_lastError = ERR_InitializeFailed;
				return;
			}
			RDX_NENDTRY(EH2)
		}
	}

	MainContext::~MainContext()
	{
		_preloader.Destroy();
		_mfpRM.Destroy();
		_mfpFS.Destroy();
		_lrm.Destroy();

		if(_objm)
		{
			_objm->Shutdown();
			_objm = NULL;
		}

		if(_rdxCodeProvider)
		{
			_rdxCodeProvider->Shutdown();
			_rdxCodeProvider = NULL;
		}
	}

	CRef<void> MainContext::LookupSymbol(OperationContext *ctx, const String *str)
	{
		return _objm->LookupSymbol(ctx, str, _zonePH);
	}
	
	void MainContext::ReleaseResource(TDP::Resources::LiveResourceHandle lrh)
	{
		if(_lrm)
			_lrm->ReleaseLRH(lrh);
	}

	void MainContext::RegisterStreamableResource(TDP::Resources::StreamableResource *sr)
	{
		if(sr->_native.previousResource || sr->_native.nextResource)
			return;		// Already registered

		sr->_native.nextResource = NULL;
		sr->_native.previousResource = _lastPendingStream;
		if(_lastPendingStream)
			_lastPendingStream->_native.nextResource = sr;
		else
			_firstPendingStream = sr;
		_lastPendingStream = sr;
	}

	// This gets called when a StreamableResource is finalized
	void MainContext::ReleaseStreamableResource(TDP::Resources::StreamableResource *sr)
	{
		// This should never happen since streaming holds the reference and prevents GC
		if(this->_activeStreamingResource == sr)
			AbortStreaming();

		if(sr->_native.nextResource)
			sr->_native.nextResource->_native.previousResource = sr->_native.previousResource;
		else
			_lastPendingStream = sr->_native.previousResource;

		if(sr->_native.nextResource)
			sr->_native.previousResource->_native.nextResource = sr->_native.nextResource;
		else
			_firstPendingStream = sr->_native.nextResource;

		_lrm->ReleaseLRH(sr->_native.resHandle);
	}

	void MainContext::PumpStreaming()
	{
		while(true)
		{
			if(_activeStreamingResource == NULL)
			{
				if(_firstPendingStream != NULL)
				{
					_activeStreamingResource = _firstPendingStream;
					if(_firstPendingStream->_native.nextResource)
					{
						_firstPendingStream->_native.nextResource->_native.previousResource = NULL;
						_firstPendingStream->_native.nextResource = NULL;
					}
					_firstPendingStream = _firstPendingStream->_native.nextResource;
					if(_activeStreamingResource != NULL)
						_lrm->BeginStreamingResource(_preloader, _activeStreamingResource, false);

					continue;
				}
				else
					break;
			}
			else
			{
				if(!_lrm->TryStream(_preloader, _activeStreamingResource))
				{
					_preloader->StopStreamingFile();

					_activeStreamingResource = NULL;
				}
				else
					break;
			}
		}
	}

	void MainContext::AbortStreaming()
	{
		if(_preloader.IsLive())
			_preloader->StopStreamingFile();

		_activeStreamingResource = NULL;
	}
}


